//
// Copyright (c) 2010 Luís Reis
//
// MIT License
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
using System.Threading;
using Pine.SourceParser.Tree;
using PPT=Pine.SourceParser.Tree;
using Pine.Core;

namespace Pine.CodeGen
{
	public delegate void ExpressionCodeGenerator(Context ctx, Expression expr, LocalBuilder lb);
	
	public delegate void StatementCodeGenerator(Context ctx, Statement stmt);
	
	public class CodeGenerator
	{
		private AssemblyBuilder assemblyBuilder;
		private ModuleBuilder moduleBuilder;
		private TypeBuilder typeBuilder;
		private ILGenerator entryPointIL;
		private MethodInfo entryPoint;
		private MethodBuilder entryPointBuilder;
		private Type type;
		private LocalBuilder globalScope;
		private static int cId = 0;
		private string assemblyExeName;
		private MethodBuilder DeclareCustomVars;
		private MethodBuilder ImportLibs;
		private static bool EnableSymInfo = false;
		
		private static IDictionary<Type, ExpressionCodeGenerator> expressionCodeGenerators = new Dictionary<Type, ExpressionCodeGenerator>();
		private static IDictionary<Type, StatementCodeGenerator> statementCodeGenerators = new Dictionary<Type, StatementCodeGenerator>();
		
		static CodeGenerator() {
			RegisterExpressionCodeGenerator(typeof(NumberConstant), NumberConstantCodeGenerator.GenerateBytecode);
			RegisterExpressionCodeGenerator(typeof(BooleanConstant), BooleanConstantCodeGenerator.GenerateBytecode);
			RegisterExpressionCodeGenerator(typeof(StringConstant), StringConstantCodeGenerator.GenerateBytecode);
			RegisterExpressionCodeGenerator(typeof(PostfixExpression), PostfixExpressionCodeGenerator.GenerateBytecode);
			RegisterExpressionCodeGenerator(typeof(NameIdentifier), NameIdentifierCodeGenerator.GenerateBytecode);
			RegisterExpressionCodeGenerator(typeof(FieldReference), FieldReferenceCodeGenerator.GenerateBytecode);
			RegisterExpressionCodeGenerator(typeof(PPT.Function), FunctionCodeGenerator.GenerateBytecode);
			RegisterExpressionCodeGenerator(typeof(BinaryOperation), BinaryOperationCodeGenerator.GenerateBytecode);
			RegisterExpressionCodeGenerator(typeof(UnaryExpression), UnaryOperationCodeGenerator.GenerateBytecode);
			RegisterExpressionCodeGenerator(typeof(CallExpression), CallExpressionCodeGenerator.GenerateBytecodeForLocal);
			RegisterExpressionCodeGenerator(typeof(ArrayAccessExpression), ArrayAccessExpressionCodeGenerator.GenerateBytecode);
			RegisterExpressionCodeGenerator(typeof(ArrayConstant), ArrayConstantCodeGenerator.GenerateBytecode);
			RegisterExpressionCodeGenerator(typeof(TypeConstant), TypeConstantCodeGenerator.GenerateBytecode);
			RegisterExpressionCodeGenerator(typeof(ListCompression), ListCompressionCodeGenerator.GenerateBytecode);
			RegisterExpressionCodeGenerator(typeof(CharConstant), CharConstantCodeGenerator.GenerateBytecode);
			RegisterExpressionCodeGenerator(typeof(NullConstant), NullConstantCodeGenerator.GenerateBytecode);
			
			RegisterStatementCodeGenerator(typeof(EmptyStatement), EmptyStatementCodeGenerator.GenerateBytecode);
			RegisterStatementCodeGenerator(typeof(ReturnStatement), ReturnStatementCodeGenerator.GenerateBytecode);
			RegisterStatementCodeGenerator(typeof(ReturnEllipsisStatement), ReturnEllipsisStatementCodeGenerator.GenerateBytecode);
			RegisterStatementCodeGenerator(typeof(Block), BlockCodeGenerator.GenerateBytecode);
			RegisterStatementCodeGenerator(typeof(ExpressionStatement), ExpressionStatementCodeGenerator.GenerateBytecode);
			RegisterStatementCodeGenerator(typeof(ConditionalStatement), ConditionalStatementCodeGenerator.GenerateBytecode);
			RegisterStatementCodeGenerator(typeof(WhileStatement), WhileStatementCodeGenerator.GenerateBytecode);
			RegisterStatementCodeGenerator(typeof(DeclarationStatement), DeclarationStatementCodeGenerator.GenerateBytecode);
			RegisterStatementCodeGenerator(typeof(ForStatement), ForStatementCodeGenerator.GenerateBytecode);
			RegisterStatementCodeGenerator(typeof(AssignmentStatement), AssignmentStatementCodeGenerator.GenerateBytecode);
			RegisterStatementCodeGenerator(typeof(BreakStatement), BreakStatementCodeGenerator.GenerateBytecode);
			RegisterStatementCodeGenerator(typeof(ContinueStatement), ContinueStatementCodeGenerator.GenerateBytecode);
			RegisterStatementCodeGenerator(typeof(DoWhileStatement), DoWhileStatementCodeGenerator.GenerateBytecode);

		}
		
		public static void RegisterExpressionCodeGenerator(Type type, ExpressionCodeGenerator codeGenerator) {
			expressionCodeGenerators.Add(type, codeGenerator);
		}
		
		public static void RegisterStatementCodeGenerator(Type type, StatementCodeGenerator codeGenerator) {
			statementCodeGenerators.Add(type, codeGenerator);
		}
		
		public CodeGenerator(string assemblyName, Document document, Library[] libs)
			: this(new AssemblyName(assemblyName), assemblyName + ".exe", document, libs) {}
		
		public CodeGenerator(AssemblyName assemblyName, string assemblyExeName, Document document, Library[] libs) {
			
			this.assemblyExeName = assemblyExeName;
			
			AppDomain appDomain = Thread.GetDomain();
			AssemblyBuilderAccess access = AssemblyBuilderAccess.RunAndSave;
			assemblyBuilder = appDomain.DefineDynamicAssembly(assemblyName, access);
			
			moduleBuilder = assemblyBuilder.DefineDynamicModule(assemblyExeName, assemblyExeName);
			typeBuilder = moduleBuilder.DefineType("PineType", TypeAttributes.Public | TypeAttributes.Class);
	
			LocalBuilder list;
			entryPointIL = CreateEntryPointStart(libs, out list);
			
			LocalBuilder builtNull = entryPointIL.DeclareLocal(typeof(NullValue));
			entryPointIL.Emit(OpCodes.Ldsfld, typeof(NullValue).GetField("Null"));
			entryPointIL.Emit(OpCodes.Stloc, builtNull);
			
			Context ctx = new Context();
			ctx.il = entryPointIL;
			ctx.callee = builtNull;
			ctx.currentMethod = null;
			ctx.list = list;
			ctx.scope = globalScope;
			ctx.assembly = assemblyBuilder;
			ctx.typeBuilder = typeBuilder;
			
			DeclareCustomVars = CreateCustomVariableDeclarationFunction(ctx, document);
			
			ctx.il.Emit(OpCodes.Ldloc, globalScope);
			ctx.il.Emit(OpCodes.Call, DeclareCustomVars);
			
			CreateEntryPointEnd(ctx);
			
			assemblyBuilder.SetEntryPoint(entryPointBuilder);
			
			type = typeBuilder.CreateType();
			entryPoint = type.GetMethod("entryPoint");
		}
		
		private static MethodBuilder CreateCustomVariableDeclarationFunction(Context ctx, Document document) {
			MethodBuilder declareCustomVars = ctx.typeBuilder.DefineMethod("DeclareCustomVars",
			                                             MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig,
			                                             typeof(void), new Type[] { typeof(IScope) });
			
			ILGenerator DeclareCustomVarsIL = declareCustomVars.GetILGenerator();
			LocalBuilder importedGlobalScope = DeclareCustomVarsIL.DeclareLocal(typeof(IScope));
			DeclareCustomVarsIL.Emit(OpCodes.Ldarg_0);
			DeclareCustomVarsIL.Emit(OpCodes.Stloc, importedGlobalScope);
			
			LocalBuilder list = DeclareCustomVarsIL.DeclareLocal(typeof(ValueList));
			LocalBuilder builtNull = DeclareCustomVarsIL.DeclareLocal(typeof(NullValue));
			DeclareCustomVarsIL.Emit(OpCodes.Ldsfld, typeof(NullValue).GetField("Null"));
			DeclareCustomVarsIL.Emit(OpCodes.Stloc, builtNull);
			
			Context declCtx = new Context(ctx);
			declCtx.il = DeclareCustomVarsIL;
			declCtx.list = list;
			declCtx.callee = builtNull;
			declCtx.scope = importedGlobalScope;
			
			foreach (SimpleVariableDeclaration decl in document.varDecls) {
				LocalBuilder lb = DeclareCustomVarsIL.DeclareLocal(typeof(Value));
				decl.Value.GenerateBytecode(declCtx, lb);
				CodeUtilities.MakeScopeDeclare(DeclareCustomVarsIL, importedGlobalScope, decl.IsConst, lb, decl.Name);
			}
			
			DeclareCustomVarsIL.Emit(OpCodes.Ret);
			
			return declareCustomVars;
		}
		
		public static void BuildFunction(Context pctx, PPT.Function func, LocalBuilder lb) {
			int funcId = cId++;
			
			string funcName;
			if (func.metadata.ContainsKey("vardecl-fname"))
				funcName = "f_" + func.metadata["vardecl-fname"];
			else
				funcName = "func_" + funcId;
			
			Context ctx = CreateFunctionStart(pctx, funcName);
			
			foreach (FunctionArgument arg in func.arguments) {
				LocalBuilder arglocal = ctx.il.DeclareLocal(typeof(Value));
				if (arg.isVarArgs) {
					ctx.il.Emit(OpCodes.Ldloc, ctx.list);
					ctx.il.Emit(OpCodes.Call, typeof(ValueList).GetMethod("Copy"));
					ctx.il.Emit(OpCodes.Stloc, arglocal);
				}
				else {
					LocalBuilder nullb = ctx.il.DeclareLocal(typeof(NullValue));
					ctx.il.Emit(OpCodes.Ldsfld, typeof(NullValue).GetField("Null"));
					ctx.il.Emit(OpCodes.Stloc, nullb);
					CodeUtilities.MakeGetListValue(ctx.il, ctx.list, arg.key, arglocal);
					CodeUtilities.MakeSetListValue(ctx.il, ctx.list, arg.key, nullb);
					if (arg.defaultValue != null) {
						Label elabel = ctx.il.DefineLabel();
						ctx.il.Emit(OpCodes.Ldloc, arglocal);
						ctx.il.Emit(OpCodes.Call, typeof(Value).GetMethod("IsNull"));
						ctx.il.Emit(OpCodes.Brfalse, elabel);
						arg.defaultValue.GenerateBytecode(ctx, arglocal);
						ctx.il.MarkLabel(elabel);
					}
				}
				CodeUtilities.MakeScopeDeclare(ctx.il, ctx.scope, arg.isConst, arglocal, arg.name);
			}
			
			CodeUtilities.MakeClearList(ctx.il, ctx.list);
			
			func.stmt.GenerateBytecode(ctx);
			
			CreateFunctionEnd(ctx);
			
			Type[] fArgs = new Type[]{ typeof(IScope), typeof(FunctionHandler) };
			pctx.il.Emit(OpCodes.Ldloc, pctx.scope);
			pctx.il.Emit(OpCodes.Ldnull);
			pctx.il.Emit(OpCodes.Ldftn, ctx.currentMethod);
			pctx.il.Emit(OpCodes.Newobj, typeof(FunctionHandler).GetConstructor(new Type[]{typeof(object), typeof(IntPtr)}));
			pctx.il.Emit(OpCodes.Newobj, typeof(Pine.Core.Function).GetConstructor(fArgs));
			pctx.il.Emit(OpCodes.Stloc, lb);
		}
		
		public static void ParseRightList(Context ctx, RightList rl) {
			if (rl is ConstantRightList) {
				ConstantRightList crl = (ConstantRightList) rl;
				
				ParseConstantRightList(ctx, crl);
				
				return;
			}
			if (rl is AssignmentRightList) {
				AssignmentRightList arl = (AssignmentRightList) rl;
				
				ConstantRightList right = arl.Right as ConstantRightList;
				
				if (right != null &&
				    right.contents.Count == 1
				    && right.contents.ContainsKey(0.0)
				    && right.contents[0.0] is CallExpression) {
					
					CallExpressionCodeGenerator.GenerateBytecodeForList(ctx, (CallExpression) right.contents[0.0]);
				}
				else {
					ParseRightList(ctx, arl.Right);
				}
				
				StoreAssignmentRightList(ctx, arl);
				
				return;
			}
			throw new Exception("TODO: Parse right list for " + rl);
		}
		
		private static void StoreAssignmentRightList(Context ctx, AssignmentRightList rl) {
		
			var contents = rl.Left.contents;
			
			LocalBuilder result = ctx.il.DeclareLocal(typeof(Value));
			
			foreach (object key in contents.Keys) {
				ctx.il.Emit(OpCodes.Ldloc, ctx.list);
				
				if (key is string) {
					ctx.il.Emit(OpCodes.Ldstr, (string) key);
					ctx.il.Emit(OpCodes.Newobj, typeof(StringValue).GetConstructor(new Type[] { typeof(string) }));
				}
				else if (key is double) {
					ctx.il.Emit(OpCodes.Ldc_R8, (double) key);
					ctx.il.Emit(OpCodes.Newobj, typeof(Number).GetConstructor(new Type[] { typeof(double) }));
				}
				else {
					throw new Exception("Invalid key type:" + key);
				}
				
				ctx.il.Emit(OpCodes.Call, typeof(ValueList).GetMethod("GetArrayElement", new Type[] { typeof(Value) }));
				ctx.il.Emit(OpCodes.Stloc, result);
				
				Expression storage = contents[key];
				if (!storage.IsMutable()) {
					throw new Exception("Expression on left side of assignment must be mutable(like a, a.b or a[b])");
				}
				
				if (storage is NameIdentifier) {
					ctx.il.Emit(OpCodes.Ldloc, ctx.scope);
					ctx.il.Emit(OpCodes.Ldstr, ((NameIdentifier) storage).name);
					ctx.il.Emit(OpCodes.Ldloc, result);
					ctx.il.Emit(OpCodes.Callvirt, typeof(IScope).GetMethod("SetItem"));
				}
				else if (storage is FieldReference) {
					LocalBuilder left = ctx.il.DeclareLocal(typeof(Value));
					((FieldReference) storage).left.GenerateBytecode(ctx, left);
					
					ctx.il.Emit(OpCodes.Ldloc, left);
					ctx.il.Emit(OpCodes.Ldstr, ((FieldReference) storage).right);
					ctx.il.Emit(OpCodes.Ldloc, result);
					ctx.il.Emit(OpCodes.Callvirt, typeof(Value).GetMethod("FieldSet"));
				}
				else if (storage is ArrayAccessExpression) {
					LocalBuilder left = ctx.il.DeclareLocal(typeof(Value));
					LocalBuilder right = ctx.il.DeclareLocal(typeof(Value));
					
					((ArrayAccessExpression) storage).left.GenerateBytecode(ctx, left);
					((ArrayAccessExpression) storage).right.GenerateBytecode(ctx, right);
					
					ctx.il.Emit(OpCodes.Ldloc, left);
					ctx.il.Emit(OpCodes.Ldloc, right);
					ctx.il.Emit(OpCodes.Ldloc, result);
					ctx.il.Emit(OpCodes.Callvirt, typeof(Value).GetMethod("SetArrayElement"));
				}
			}
		}
		
		private static void ParseConstantRightList(Context ctx, ConstantRightList rl) {
			LocalBuilder tmpList = ctx.il.DeclareLocal(typeof(ValueList));
			ctx.il.Emit(OpCodes.Newobj, typeof(ValueList).GetConstructor(Type.EmptyTypes));
			ctx.il.Emit(OpCodes.Stloc, tmpList);
			
			Context nCtx = new Context(ctx);
			nCtx.list = tmpList;
			
			foreach (object key in rl.contents.Keys) {
				ctx.il.Emit(OpCodes.Ldloc, tmpList);
				ctx.il.Emit(OpCodes.Call, typeof(ValueList).GetMethod("Clear"));
				
				LocalBuilder val = ctx.il.DeclareLocal(typeof(Value));
				if (rl.contents[key] == null) {
					Console.WriteLine("null contents for " + key);
				}
				rl.contents[key].GenerateBytecode(nCtx, val);
				
				CodeUtilities.MakeSetListValue(ctx.il, ctx.list, key, val);
			}
		}
		
		public static void ParseStatement(Context ctx, Statement stmt) {
			foreach (Type type in statementCodeGenerators.Keys) {
				if (type.IsInstanceOfType(stmt)) {
					statementCodeGenerators[type](ctx, stmt);
					return;
				}
			}
			
			throw new Exception("TODO: Make statement for " + stmt.GetType());
		}
		
		public static void ParseExpression(Context ctx, Expression expr, LocalBuilder lb) {
			if (expr == null)
				throw new ArgumentNullException("expr");
			
			foreach (Type type in expressionCodeGenerators.Keys) {
				if (type.IsInstanceOfType(expr)) {
					expressionCodeGenerators[type](ctx, expr, lb);
					return;
				}
			}
			
			throw new Exception("TODO: Could not generate expression for " + expr.ToString());
		}
		
		private static void MakeLocalVariableStore(ILGenerator ilGenerator, LocalBuilder local) {
			ilGenerator.Emit(OpCodes.Stloc, local);
		}
		
		private static void MakeStackValueCall(ILGenerator ilGenerator, LocalBuilder list) {
			ilGenerator.Emit(OpCodes.Ldloc, list);
			ilGenerator.Emit(OpCodes.Callvirt, typeof(Value).GetMethod("Invoke"));
		}
		
		private static void MakeValueCall(ILGenerator ilGenerator, LocalBuilder local, LocalBuilder list) {
			ilGenerator.Emit(OpCodes.Ldloc, local);
			ilGenerator.Emit(OpCodes.Ldloc, list);
			ilGenerator.Emit(OpCodes.Callvirt, typeof(Value).GetMethod("Invoke"));
		}
		
		private static Context CreateFunctionStart(Context pctx, string fname) {
			MethodAttributes attributes = MethodAttributes.Public | MethodAttributes.Static
				| MethodAttributes.HideBySig;
			Type[] arguments = new Type[] { typeof(Pine.Core.Function), typeof(ValueList) };
			MethodBuilder builder = pctx.typeBuilder.DefineMethod(fname, attributes, typeof(void), arguments);
			ILGenerator ilGenerator = builder.GetILGenerator();
			
			LocalBuilder callee = ilGenerator.DeclareLocal(typeof(Pine.Core.Function));
			if (EnableSymInfo)
				callee.SetLocalSymInfo("callee");
			
			LocalBuilder scope = ilGenerator.DeclareLocal(typeof(IScope));
			if (EnableSymInfo)
				scope.SetLocalSymInfo("scope");
			
			LocalBuilder args = ilGenerator.DeclareLocal(typeof(ValueList));
			if (EnableSymInfo)
				args.SetLocalSymInfo("list");
			
			ilGenerator.Emit(OpCodes.Ldarg_0);
			MakeLocalVariableStore(ilGenerator, callee);
			ilGenerator.Emit(OpCodes.Ldarg_1);
			MakeLocalVariableStore(ilGenerator, args);
			
			ilGenerator.Emit(OpCodes.Ldloc, callee);
			MethodInfo getScope = typeof(Pine.Core.Function).GetMethod("get_Scope", Type.EmptyTypes);
			ilGenerator.Emit(OpCodes.Call, getScope);
			MakeLocalVariableStore(ilGenerator, scope);
			
			Context ctx = new Context(pctx);
			ctx.currentMethod = builder;
			ctx.il = ilGenerator;
			ctx.callee = callee;
			ctx.list = args;
			ctx.scope = scope;
			ctx.continueLabels.Clear();
			ctx.breakLabels.Clear();
			ctx.loopNames.Clear();
			
			return CodeUtilities.MakeChildScope(ctx);
		}
		
		private static void CreateFunctionEnd(Context ctx) {
			ctx.il.Emit(OpCodes.Ret);
		}
		
		private void GenerateLibsIL(Library[] libs, ILGenerator il, LocalBuilder scope) {
			foreach (Library lib in libs) {
				lib.GenerateIL(il, scope);
			}
		}
		
		private MethodBuilder GenerateImportLibs(Library[] libs) {
			MethodAttributes attributes = MethodAttributes.Public | MethodAttributes.Static
				| MethodAttributes.HideBySig;
			MethodBuilder importLibs = typeBuilder.DefineMethod("ImportLibs", attributes, typeof(void), new Type[] { typeof(IScope) });
			
			ILGenerator il = importLibs.GetILGenerator();
			LocalBuilder globalScope = il.DeclareLocal(typeof(IScope));
			
			il.Emit(OpCodes.Ldarg_0);
			il.Emit(OpCodes.Stloc, globalScope);
			
			GenerateLibsIL(libs, il, globalScope);
			
			il.Emit(OpCodes.Ret);
			
			return importLibs;
		}
		
		private ILGenerator CreateEntryPointStart(Library[] libs, out LocalBuilder list) {
			MethodAttributes attributes = MethodAttributes.Public | MethodAttributes.Static
				| MethodAttributes.HideBySig;
			Type[] arguments = Type.EmptyTypes;
			MethodBuilder builder = typeBuilder.DefineMethod("entryPoint", attributes, typeof(void), arguments);
			entryPointBuilder = builder;
			
			ILGenerator ilGenerator = builder.GetILGenerator();
			ilGenerator.Emit(OpCodes.Newobj, typeof(ValueList).GetConstructor(Type.EmptyTypes));
			list = ilGenerator.DeclareLocal(typeof(ValueList));
			MakeLocalVariableStore(ilGenerator, list);
			ilGenerator.Emit(OpCodes.Newobj, typeof(BlockScope).GetConstructor(Type.EmptyTypes));
			
			globalScope = ilGenerator.DeclareLocal(typeof(IScope));
			if (EnableSymInfo)
				globalScope.SetLocalSymInfo("globalScope");
			
			MakeLocalVariableStore(ilGenerator, globalScope);
			
			ImportLibs = GenerateImportLibs(libs);
			
			ilGenerator.Emit(OpCodes.Ldloc, globalScope);
			ilGenerator.Emit(OpCodes.Call, ImportLibs);
			
			return ilGenerator;
		}
		
		private void CreateEntryPointEnd(Context ctx) {
			
			CodeUtilities.MakeScopeRead(ctx, "main");
			MakeStackValueCall(ctx.il, ctx.list);
			
			ctx.il.Emit(OpCodes.Ret);
		}
		
		public void Invoke() {
			entryPoint.Invoke(null, new object[] {});
		}
		
		public void ExecuteFunction(string function, ValueList rl, bool importLibs) {
			IScope scope = new BlockScope(null);
			
			if (importLibs)
				type.GetMethod("ImportLibs").Invoke(null, new object[] { scope });
			
			type.GetMethod("DeclareCustomVars").Invoke(null, new object[] { scope });
			
			scope.GetItem(function).Invoke(rl);
		}
		
		public void Save() {
			assemblyBuilder.Save(assemblyExeName);
		}
	}
}
